﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;

using Texture2D = Microsoft.Xna.Framework.Graphics.Texture2D;
using xColor = Microsoft.Xna.Framework.Color;

namespace MaterialEditor
{
    public partial class Form1 : Form
    {
        public bool Looping = true;

        public static Form1 instance;

        /// <summary>
        /// Represents all redo actions currently queued
        /// </summary>
        private Stack<IAction> redoActions = new Stack<IAction>();
        /// <summary>
        /// Represents all undo actions currently queued
        /// </summary>
        private Stack<IAction> undoActions = new Stack<IAction>();
       
        private Point placeNodePoint;

        DateTime timePrev = DateTime.Now;

        /// <summary>
        /// The node currently being edited
        /// </summary>
        private Node editNode;

        /// <summary>
        /// Services container for xna content loading
        /// </summary>
        private ServiceContainer services = new ServiceContainer();
        public ServiceContainer Services
        {
            get { return services; }
        }

        /// <summary>
        /// Content builder for xna content building/loading
        /// </summary>
        public ContentBuilder contentBuilder;

        /// <summary>
        /// Content builder for xna content building/loading
        /// </summary>
        public Microsoft.Xna.Framework.Content.ContentManager content;

        public OutputNode output;

        public Graphics graphics;

        /// <summary>
        /// Represents the current time in the animation
        /// </summary>
        public TimeSpan Now
        {
            get { return timeline.playbackPannel1.currentTime; }
        }

        /// <summary>
        /// The timeline window
        /// </summary>
        public Timeline timeline;

        public Form1()
        {
            InitializeComponent();

            // Inilitize graphics
            graphics = Graphics.Inilitize(viewWindow1);

            // create services for xna content loading
            services.AddService<Microsoft.Xna.Framework.Graphics.IGraphicsDeviceService>(new GraphicsDeviceService(graphics.device));

            // create content builder and manager
            contentBuilder = new ContentBuilder();

            content = new Microsoft.Xna.Framework.Content.ContentManager(services, contentBuilder.OutputDirectory);  
          
            FormClosing += Form1_FormClosing;

            output = new OutputNode(1500, 500);
            viewWindow1.nodes.Add(output);

            // create timeline window
            timeline = new Timeline();

            // create render preview window
            renderPreviewWindow = new RenderWindow();
            renderPreviewWindow.cubeRender1.Inilitize();
        }

        private void viewWindow1_MouseClick(object sender, MouseEventArgs e)
        {
            // if the right mouse button was clicked
            if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                // store a value to indicate if a node has been selected
                bool nodesSelected = viewWindow1.pickupNodes.Count() > 0;
                if (nodesSelected)
                {
                    // if only one node is selected and it can be edited, enable the edit button
                    if (viewWindow1.pickupNodes.Count() == 1 && viewWindow1.pickupNodes.First().CanEdit)
                    {
                        editToolStripMenuItem1.Enabled = true;

                        viewWindow1.selectedNode = viewWindow1.pickupNodes.First();
                        editNode = viewWindow1.pickupNodes.First();
                    }
                    removeNodeMenuStrip.Show(viewWindow1, e.Location);
                    // if there is more than one node selected, allow the user to group the nodes
                    if (viewWindow1.pickupNodes.Count() > 1)
                    {
                        groupSeperator.Visible = true;
                        groupToolStripMenuItem.Visible = true;
                    }
                    else
                    {
                        groupSeperator.Visible = false;
                        groupToolStripMenuItem.Visible = false;
                    }
                    ungroupToolStripMenuItem.Visible = false;
                    // if a node is grouped, enable the option to ungroup it
                    foreach (Node n in viewWindow1.pickupNodes)
                        if (n.group != null)
                        {
                            ungroupToolStripMenuItem.Visible = true;
                            break;
                        }
                    
                }
                // if no node was found, allow the user to create a node
                else
                {
                    addItemMenuStrip.Show(viewWindow1, e.Location);
                    // transform and store the location of the menu
                    Microsoft.Xna.Framework.Vector2 v = new Microsoft.Xna.Framework.Vector2(e.Location.X, e.Location.Y);
                    v = Microsoft.Xna.Framework.Vector2.Transform(v,
                        Microsoft.Xna.Framework.Matrix.Invert(Graphics.Instance.Camera));
                    placeNodePoint = new Point((int)v.X, (int)v.Y);
                    editToolStripMenuItem1.Enabled = false;
                }
            }
        }

        void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (undoActions.Count == 0)
            {
                Looping = false;
                return;
            }
            // allow last-chance save on close
            DialogResult result = MessageBox.Show("All unsaved progress will be lost, are you sure?", "Continue Without Saving?", MessageBoxButtons.YesNoCancel);
            if (result == System.Windows.Forms.DialogResult.Yes)
                Looping = false;
            else
                e.Cancel = true;
        }

        #region Undo/Redo
        public void AddUndoAction(IAction action)
        {
            redoActions.Clear();
            redoToolStripMenuItem.Enabled = false;

            undoActions.Push(action);
        }

        public void AddRedoAction(IAction action)
        {
            redoToolStripMenuItem.Enabled = true;
            redoActions.Push(action);
        }

        private void undoToolStripMenuItem_Click(object sender, EventArgs e)
        {
            try
            {
                IAction a = undoActions.Pop();

                if (a != null)
                {
                    a.Undo();
                    redoActions.Push(a);
                    redoToolStripMenuItem.Enabled = true;
                }
            }
            catch (InvalidOperationException ee)
            {
                // fuck this stupid exception
            }
        }

        private void redoToolStripMenuItem_Click(object sender, EventArgs e)
        {
            IAction a = redoActions.Pop();

            if (a != null)
            {
                a.Redo();
                undoActions.Push(a);
            }
        }
        #endregion

        #region Update/Draw

        static bool firstFrame = false;
        public new void Update()
        {
            viewWindow1.Update();
            mousePointPrev = viewWindow1.PointToClient(MousePosition);

            DynamicTexture.Update();

            if (renderPreviewWindow != null)
                renderPreviewWindow.cubeRender1.Update(0.1f);

            timeline.DoUpdate();
        }

        public void Draw()
        {
            Graphics g = Graphics.Instance;
            g.ClearBackBuffer(Color.FromArgb(255, 10, 10, 10));
            viewWindow1.Draw();
            g.Present();

            if (renderPreviewWindow != null)
                renderPreviewWindow.cubeRender1.Draw();
        }

        #endregion

        #region Add Nodes
        private void tessellateToolStripMenuItem_Click(object sender, EventArgs e)
        {
            viewWindow1.nodes.Add(new TesselateNode(placeNodePoint.X, placeNodePoint.Y));
        }

        private void rotateToolStripMenuItem_Click(object sender, EventArgs e)
        {
            viewWindow1.nodes.Add(new RotateNode(placeNodePoint.X, placeNodePoint.Y));
        }

        private void addToolStripMenuItem_Click(object sender, EventArgs e)
        {
            viewWindow1.nodes.Add(new AddNode(placeNodePoint.X, placeNodePoint.Y));
        }

        private void decimalToolStripMenuItem_Click(object sender, EventArgs e)
        {
            NumberPicker numberPicker = new NumberPicker();
            numberPicker.FormClosing += numberPicker_FormClosing;
            numberPicker.Show();
        }
        void numberPicker_FormClosing(object sender, FormClosingEventArgs e)
        {
            NumberPicker form = sender as NumberPicker;
            if (form.DialogResult == System.Windows.Forms.DialogResult.OK)
                viewWindow1.nodes.Add(new ConstantValueNode(placeNodePoint.X, placeNodePoint.Y, new NodeValue((float)form.numericUpDown1.Value)));
        }

        private void multiplyToolStripMenuItem_Click(object sender, EventArgs e)
        {
            viewWindow1.nodes.Add(new MultiplyNode(placeNodePoint.X, placeNodePoint.Y));
        }

        private void colorToolStripMenuItem_Click(object sender, EventArgs e)
        {
            DialogResult result = colorDialog1.ShowDialog();
            if (result == System.Windows.Forms.DialogResult.OK)
            {
                Color c = colorDialog1.Color;
                Microsoft.Xna.Framework.Color xColor = new Microsoft.Xna.Framework.Color(c.R, c.G, c.B, c.A);
                viewWindow1.nodes.Add(new ConstantValueNode(placeNodePoint.X, placeNodePoint.Y, new NodeValue(xColor)));
            }
        }

        private void invertToolStripMenuItem_Click(object sender, EventArgs e)
        {
            InvertNode node = new InvertNode(placeNodePoint.X, placeNodePoint.Y);
            viewWindow1.nodes.Add(node);
        }

        private void vector2ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            VectorPicker v2Picker = new VectorPicker(false);
            v2Picker.Show(this);
            v2Picker.FormClosing += v2Picker_FormClosing;
        }

        void v2Picker_FormClosing(object sender, FormClosingEventArgs e)
        {
            VectorPicker p = sender as VectorPicker;

            if (p.DialogResult == System.Windows.Forms.DialogResult.OK)
            {
                Microsoft.Xna.Framework.Vector2 vector = new Microsoft.Xna.Framework.Vector2((float)p.xValue.Value, (float)p.yValue.Value);
                ConstantValueNode v = new ConstantValueNode(placeNodePoint.X, placeNodePoint.Y, new NodeValue(vector));
                viewWindow1.nodes.Add(v);
            }
        }

        private void vector3ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            VectorPicker v3Picker = new VectorPicker(true);
            v3Picker.Show(this);
            v3Picker.FormClosing += v3Picker_FormClosing;

        }

        void v3Picker_FormClosing(object sender, FormClosingEventArgs e)
        {
            VectorPicker p = sender as VectorPicker;

            if (p.DialogResult == System.Windows.Forms.DialogResult.OK)
            {
                Microsoft.Xna.Framework.Vector3 vector = new Microsoft.Xna.Framework.Vector3((float)p.xValue.Value, (float)p.yValue.Value, (float)p.zValue.Value);
                ConstantValueNode v = new ConstantValueNode(placeNodePoint.X, placeNodePoint.Y, new NodeValue(vector));
                viewWindow1.nodes.Add(v);
            }
        }

        private void lerpToolStripMenuItem_Click(object sender, EventArgs e)
        {
            LerpNode n = new LerpNode(placeNodePoint.X, placeNodePoint.Y);
            viewWindow1.nodes.Add(n);
        }

        private void subtractToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            SubtractNode n = new SubtractNode(placeNodePoint.X, placeNodePoint.Y);
            viewWindow1.nodes.Add(n);
        }

        private void panToolStripMenuItem_Click(object sender, EventArgs e)
        {
            PanNode n = new PanNode(placeNodePoint.X, placeNodePoint.Y);
            viewWindow1.nodes.Add(n);
        }
        #endregion
        
        private void textureToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenFileDialog dialog = new OpenFileDialog();
            dialog.Filter = "Png Files | *.png;";
            DialogResult result = dialog.ShowDialog();

            if (result == System.Windows.Forms.DialogResult.OK)
            {
                try
                {
                    FileInfo file = new FileInfo(dialog.FileName);
                    if (file.Extension == ".tga")
                    {
                        Bitmap b = Image.FromFile(file.FullName) as Bitmap;
                        Microsoft.Xna.Framework.Graphics.Texture2D tex = new Texture2D(Graphics.Instance.device, b.Width, b.Height);
                        xColor[] newData = new xColor[b.Width * b.Height];
                        for (int y = 0; y < b.Height; y++)
                        {
                            for (int x = 0; x < b.Width; x++)
                            {
                                System.Drawing.Color c = b.GetPixel(x, y);
                                newData[x + y * b.Width] = new xColor(c.R, c.G, c.B, c.A);
                            }
                        }
                        tex.SetData(newData);
                        TextureContainer t = new TextureContainer(tex);
                        viewWindow1.nodes.Add(new ConstantValueNode(placeNodePoint.X, placeNodePoint.Y, new NodeValue(t)));
                    }
                    else
                    {
                        Microsoft.Xna.Framework.Graphics.Texture2D tex = Microsoft.Xna.Framework.Graphics.Texture2D.FromStream(Graphics.Instance.device, new System.IO.FileStream(dialog.FileName, System.IO.FileMode.Open));
                        TextureContainer t = new TextureContainer(tex);
                        viewWindow1.nodes.Add(new ConstantValueNode(placeNodePoint.X, placeNodePoint.Y, new NodeValue(t)));
                    }
                }
                catch (System.IO.IOException ee)
                {
                    MessageBox.Show(ee.Message);
                }
            }
        }

        private void editToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            // Find the node to edit
            ConstantValueNode c = editNode as ConstantValueNode;
            DialogResult result = System.Windows.Forms.DialogResult.None;

            // Create the node based on its type
            switch (c.value.type)
            {
                case NodeValueType.ValueColor:
                    result = colorDialog1.ShowDialog();
                    if (result == System.Windows.Forms.DialogResult.OK)
                    {
                        Color color = colorDialog1.Color;
                        Microsoft.Xna.Framework.Color xColor = (new Microsoft.Xna.Framework.Color(color.R, color.G, color.B, color.A));
                        c.value.Value = xColor;
                    }
                    break;
                case NodeValueType.ValueFloat:
                    NumberPicker picker = new NumberPicker();
                    picker.FormClosing += picker_FormClosing;
                    picker.Show();
                    break;
                case NodeValueType.ValueTexture:
                    OpenFileDialog dialog = new OpenFileDialog();
                    dialog.Filter = "Png Files | *.png;";
                    result = dialog.ShowDialog();
                    if (result == System.Windows.Forms.DialogResult.OK)
                    {
                        c.value.Value = Microsoft.Xna.Framework.Graphics.Texture2D.FromStream(Graphics.Instance.device, new System.IO.FileStream(dialog.FileName, System.IO.FileMode.Open));
                    }
                    break;
                case NodeValueType.ValueVector2:
                    Microsoft.Xna.Framework.Vector2 v2 = (Microsoft.Xna.Framework.Vector2)((ConstantValueNode)editNode).value.Value;
                    VectorPicker v2EditPicker = new VectorPicker(false, new Microsoft.Xna.Framework.Vector3(v2.X, v2.Y, 0));
                    v2EditPicker.FormClosing += v2EditPicker_FormClosing;
                    v2EditPicker.Show();
                    break;
                case NodeValueType.ValueVector3: 
                    Microsoft.Xna.Framework.Vector3 v3 = (Microsoft.Xna.Framework.Vector3)((ConstantValueNode)editNode).value.Value;
                    VectorPicker v3EditPicker = new VectorPicker(true, new Microsoft.Xna.Framework.Vector3(v3.X, v3.Y, v3.Z));
                    v3EditPicker.FormClosing += v3EditPicker_FormClosing;
                    v3EditPicker.Show();
                    break;
            }
        }

        void v3EditPicker_FormClosing(object sender, FormClosingEventArgs e)
        {
            VectorPicker v = sender as VectorPicker;
            float x = (float)v.xValue.Value;
            float y = (float)v.yValue.Value;
            float z = (float)v.zValue.Value;

            ((ConstantValueNode)editNode).value.Value = new Microsoft.Xna.Framework.Vector3(x, y, z);
        }

        void v2EditPicker_FormClosing(object sender, FormClosingEventArgs e)
        {
            VectorPicker v = sender as VectorPicker;
            float x = (float)v.xValue.Value;
            float y = (float)v.yValue.Value;

            ((ConstantValueNode)editNode).value.Value = new Microsoft.Xna.Framework.Vector2(x, y);
        }

        void picker_FormClosing(object sender, FormClosingEventArgs e)
        {
            NumberPicker picker = sender as NumberPicker;
            ConstantValueNode c = editNode as ConstantValueNode;
            c.value.Value = (float)picker.numericUpDown1.Value;
        }

        private void removeToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            while (viewWindow1.pickupNodes.Count() != 0)
            {
                Node n = viewWindow1.pickupNodes.First();
                foreach (NodeConnector c in n.AttachedConnectors)
                    c.Disconnect();
                viewWindow1.nodes.Remove(n);
            }
        }

        private Point mousePointPrev;
        private void viewWindow1_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Middle)
            {
                Cursor = Cursors.SizeAll;

                Graphics.Instance.cameraPosition.X -= (mousePointPrev.X - e.X);
                Graphics.Instance.cameraPosition.Y -= (mousePointPrev.Y - e.Y);
            }
        }

        private void viewWindow1_MouseUp(object sender, MouseEventArgs e)
        {
            Cursor = Cursors.Default;
        }

        private void groupToolStripMenuItem_Click(object sender, EventArgs e)
        {
            NameForm f = new NameForm("Group " + viewWindow1.groupBoxes.Count().ToString());
            f.FormClosing += f_FormClosing;
            f.Show();
        }

        void f_FormClosing(object sender, FormClosingEventArgs e)
        {
            NameForm f = sender as NameForm;
            NodeGroupBox box = new NodeGroupBox(viewWindow1.pickupNodes, f.textBox1.Text);
            viewWindow1.groupBoxes.Add(box);
        }

        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete || e.KeyCode == Keys.Back)
            {
                removeToolStripMenuItem1_Click(sender, e);
            }
        }

        private void ungroupToolStripMenuItem_Click(object sender, EventArgs e)
        {
            foreach (Node n in viewWindow1.pickupNodes)
                n.UnGroup();
        }

        private void selectAllToolStripMenuItem_Click(object sender, EventArgs e)
        {
            foreach (Node n in viewWindow1.nodes)
                n.Pickup = true;
        }

        public RenderWindow renderPreviewWindow;
        private void toolStripMenuItem1_Click(object sender, EventArgs e)
        {
            renderPreviewWindow.Show();
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        void WriteTexture(BinaryWriter writer, int width, int height, xColor[] data)
        {
            writer.Write(width);
            writer.Write(height);
            for (int i = 0; i < data.Length; i++)
            {
                writer.Write(data[i].A);
                writer.Write(data[i].R);
                writer.Write(data[i].G);
                writer.Write(data[i].B);
            }
        }

        private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SaveFileDialog dialog = new SaveFileDialog();
            dialog.Filter = "Materials | *.mat | All Files | *.*";
            DialogResult result = dialog.ShowDialog();

            if (result == System.Windows.Forms.DialogResult.OK)
            {
                // get textures
                Texture2D diffuseMap = output.difuseInput.value.AsTexture2D();
                Texture2D normalMap = output.normalInput.value != null ? output.normalInput.value.AsTexture2D() : null;
                Texture2D specularMap = output.specularInput.value != null ? output.specularInput.value.AsTexture2D() : null;

                // def texture data
                xColor[] diffuse_data = new xColor[diffuseMap.Width * diffuseMap.Height];
                xColor[] normal_data = null;
                xColor[] specular_data = null;

                // get texture data
                diffuseMap.GetData(diffuse_data);

                if (normalMap != null)
                {
                    normal_data = new xColor[normalMap.Width * normalMap.Height];
                    normalMap.GetData(normal_data);
                }

                if (specularMap != null)
                {
                    specular_data = new xColor[specularMap.Width * specularMap.Height];
                    specularMap.GetData(specular_data);
                }
                // write texture data
                using (BinaryWriter writer = new BinaryWriter(new FileStream(dialog.FileName, FileMode.OpenOrCreate)))
                {
                    // write diffuse data
                    WriteTexture(writer, diffuseMap.Width, diffuseMap.Height, diffuse_data);
                    // write normal data
                    writer.Write(normalMap != null);
                    if (normalMap != null)
                        WriteTexture(writer, normalMap.Width, normalMap.Height, normal_data);
                    // write specular data
                    writer.Write(specularMap != null);
                    if (specularMap != null)
                        WriteTexture(writer, specularMap.Width, specularMap.Height, specular_data);
                }
            }
        }

        private bool updateTimeline;

        private void timelineToolStripMenuItem_Click(object sender, EventArgs e)
        {
            timeline.Show();

        }

        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {

        }
    }


    public class GraphicsDeviceService : Microsoft.Xna.Framework.Graphics.IGraphicsDeviceService
    {
        private Microsoft.Xna.Framework.Graphics.GraphicsDevice device;

        public GraphicsDeviceService(Microsoft.Xna.Framework.Graphics.GraphicsDevice device)
        {
            this.device = device;
        }

        public event EventHandler<EventArgs> DeviceCreated;

        public event EventHandler<EventArgs> DeviceDisposing;

        public event EventHandler<EventArgs> DeviceReset;

        public event EventHandler<EventArgs> DeviceResetting;

        public Microsoft.Xna.Framework.Graphics.GraphicsDevice GraphicsDevice
        {
            get { return device; }
        }
    }
}
